package com.yangrd.easypress;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.*;

import javax.persistence.EntityNotFoundException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

/**
 * @author yangrd
 * @date 2019/3/22
 **/
@Slf4j

@RestController
@RequestMapping("/admin")
public class CtlHelper implements ApplicationContextAware {

    private MappingManager mappingManager;

    @PostMapping("/{repository}")
    public ResponseEntity create(@PathVariable String repository, @RequestBody String reqJSON) {
        return mappingManager.getJpaRepository(repository).map(repo -> {
            Object object = mappingManager.getEntityObj(repository);
            Object req = JSON.parseObject(reqJSON, object.getClass());
            BeanUtils.copyProperties(req, object);
            return ResponseEntity.status(HttpStatus.CREATED).body(repo.saveAndFlush(object));
        }).
                orElseGet(() -> ResponseEntity.notFound().build());
    }

    @DeleteMapping("/{repository}/{id}")
    public ResponseEntity delete(@PathVariable String repository, @PathVariable Long id) {
        return mappingManager.getJpaRepository(repository).map(repo -> {
            repo.deleteById(id);
            return ResponseEntity.noContent().build();
        }).
                orElseGet(() -> ResponseEntity.notFound().build());
    }

    @PutMapping("/{repository}/{id}")
    public ResponseEntity update(@PathVariable String repository, @PathVariable Long id, @RequestBody String reqJSON) {
        return mappingManager.getJpaRepository(repository).map(repo -> {
            repo.findById(id).ifPresent(db -> {
                Object req = JSON.parseObject(reqJSON, db.getClass());
                BeanUtils.copyProperties(req, db);
                repo.saveAndFlush(db);
            });
            return ResponseEntity.noContent().build();
        }).
                orElseGet(() -> ResponseEntity.notFound().build());
    }

    @GetMapping("/{repository}/{id}")
    public ResponseEntity get(@PathVariable String repository, @PathVariable Long id) {
        return mappingManager.getJpaRepository(repository).map(repo -> ResponseEntity.ok(repo.findById(id))).
                orElseGet(() -> ResponseEntity.notFound().build());
    }

    @GetMapping("/{repository}")
    public ResponseEntity get(@PathVariable String repository, Pageable pageable) {
        return mappingManager.getJpaRepository(repository).map(repo -> ResponseEntity.ok(repo.findAll(pageable))).
                orElseGet(() -> ResponseEntity.notFound().build());
    }

    class MappingManager {
        private Map<String, JpaRepository> entity4Repositories = new HashMap<>();
        private Map<String, Class> entity4Class = new HashMap<>();

        MappingManager(ApplicationContext applicationContext) {
            Map<String, JpaRepository> repositoryBeans = applicationContext.getBeansOfType(JpaRepository.class);
            repositoryBeans.forEach((repositoryName, repositoryBean) -> {
                Class entityClass = MappingSupport.getEntityClass(repositoryBean);
                String entityClassName = MappingSupport.getEntityName(entityClass);
                entity4Repositories.put(entityClassName, repositoryBean);
                entity4Class.put(entityClassName, entityClass);
            });
        }

        public Optional<JpaRepository> getJpaRepository(String repository) {
            return Optional.ofNullable(entity4Repositories.get(repository));
        }

        public Object getEntityObj(String repository) {
            return Optional.ofNullable(entity4Class.get(repository)).map(clazz -> {
                try {
                    return clazz.newInstance();
                } catch (InstantiationException | IllegalAccessException e) {
                    e.printStackTrace();
                }
                return null;
            }).orElseThrow(EntityNotFoundException::new);
        }
    }

    static class MappingSupport {
        static Class getEntityClass(JpaRepository jpaRepository) {
            Type[] jpaInterfacesTypes = jpaRepository.getClass().getGenericInterfaces();
            Type[] type = ((ParameterizedType) ((Class) jpaInterfacesTypes[0]).getGenericInterfaces()[0]).getActualTypeArguments();
            return (Class) type[0];
        }

        static String getEntityName(Class clazz) {
            String simpleName = clazz.getSimpleName();
            return simpleName.substring(0, 1).toLowerCase() + simpleName.substring(1);
        }
    }

    /**
     * @author yangrd
     * @date 2018/8/30
     **/
    static class BeanUtils {

        /**
         * 只拷贝不为null的属性
         *
         * @param source the source bean
         * @param target the target bean
         * @throws BeansException if the copying failed
         */
        public static void copyProperties(Object source, Object target) throws BeansException {
            org.springframework.beans.BeanUtils.copyProperties(source, target, getNullPropertyNames(source));
        }

        private static String[] getNullPropertyNames(Object source) {
            final BeanWrapper src = new BeanWrapperImpl(source);
            PropertyDescriptor[] pds = src.getPropertyDescriptors();

            Set<String> emptyNames = new HashSet<>();
            for (PropertyDescriptor pd : pds) {
                Object srcValue = src.getPropertyValue(pd.getName());
                if (srcValue == null) {
                    emptyNames.add(pd.getName());
                }
            }
            String[] result = new String[emptyNames.size()];

            return emptyNames.toArray(result);
        }
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Assert.notNull(applicationContext, "");
        this.mappingManager = new MappingManager(applicationContext);
    }
}
